package gridfs.dao;

import com.mongodb.MongoClient;
import com.mongodb.ReadConcern;
import com.mongodb.WriteConcern;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoCursor;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.UpdateOptions;
import jnr.ffi.Pointer;
import org.bson.Document;
import org.bson.types.Binary;
import org.bson.types.ObjectId;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;

import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;

/**
 * @author siuming
 */
final class GridFileBucket {
    private MongoDatabase database;
    private ReadConcern readConcern;
    private WriteConcern writeConcern;
    private String bucketName;
    private int chunkSize;

    GridFileBucket(MongoDatabase database, ReadConcern readConcern, WriteConcern writeConcern, String bucketName, int chunkSize) {
        this.database = database;
        this.readConcern = readConcern;
        this.writeConcern = writeConcern;
        this.bucketName = bucketName;
        this.chunkSize = chunkSize;
    }

    GridFile get(String filename) {
        GridFileGet action = new GridFileGet(
                getFilesCollection(database, bucketName).withReadConcern(readConcern)
        );
        return action.exec(filename);
    }

    ObjectId create(String filename, String dirname, Document metadata) {
        ObjectId id = new ObjectId();
        GridFileCreate action = new GridFileCreate(
                getFilesCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern),
                getChunkCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern),
                chunkSize
        );
        action.exec(
                id,
                filename,
                dirname,
                0,
                metadata
        );
        return id;
    }

    void delete(ObjectId id) {
        GridFileDelete action = new GridFileDelete(
                getFilesCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern),
                getChunkCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern)
        );
        action.exec(id);
    }

    void write(ObjectId id, Pointer buffer, long offset, long bufSize) {
        GridFileWrite action = new GridFileWrite(
                getFilesCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern),
                getChunkCollection(database, bucketName).withReadConcern(readConcern).withWriteConcern(writeConcern)
        );
        action.exec(id, buffer, offset, bufSize);
    }

    byte[] read(ObjectId id, long size, long offset) {
        GridFileRead action = new GridFileRead(
                getFilesCollection(database, bucketName).withReadConcern(readConcern),
                getChunkCollection(database, bucketName).withReadConcern(readConcern)
        );

        return action.exec(id, size, offset);
    }

    List<String> readdir(String dirname) {
        MongoCollection<Document> filesCollection = getFilesCollection(database, bucketName).withReadConcern(readConcern);
        MongoCursor<Document> itr = filesCollection.find(Filters.eq("dirname",dirname)).iterator();
        List<String> dirList = new LinkedList<String>();
        while(itr.hasNext()){
            Document d = itr.next();
            String filename = d.getString("filename");
            dirList.add(filename.substring(filename.lastIndexOf('/')+1));
        }
        return dirList;
    }

    void truncate(ObjectId id, long length) {
        MongoCollection<Document> filesCollection = getFilesCollection(database, bucketName).withReadConcern(readConcern);
        filesCollection.updateOne(
                new Document("_id", id),
                new Document("$set", new Document("length", length)),
                new UpdateOptions().upsert(true)
        );
    }

    void rename(ObjectId id, String newName) {
        MongoCollection<Document> filesCollection = getFilesCollection(database, bucketName).withReadConcern(readConcern);
        filesCollection.updateOne(
                new Document("_id", id),
                new Document("$set", new Document("filename", newName)),
                new UpdateOptions().upsert(true)
        );
    }

    private static MongoCollection<Document> getFilesCollection(final MongoDatabase database, final String bucketName) {
        return database
                .getCollection(bucketName + ".files")
                .withCodecRegistry(fromRegistries(database.getCodecRegistry(), MongoClient.getDefaultCodecRegistry()));
    }

    private static MongoCollection<Document> getChunkCollection(final MongoDatabase database, final String bucketName) {
        return database
                .getCollection(bucketName + ".chunks")
                .withCodecRegistry(MongoClient.getDefaultCodecRegistry());
    }
}
